package org.dftproject.lineagelinkage.core.agents;

import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dftproject.genesis.core.persistence.bdb.PersistenceBinding;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.lineagelinkage.core.Activator;
import org.eclipse.core.runtime.Platform;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;

import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

public class FindDuplicatesAgentState {

	private static final Log log = LogFactory.getLog(FindDuplicatesAgentState.class);

	protected static FindDuplicatesAgentState singleton = new FindDuplicatesAgentState();
	
	private Environment environment;
	private final Map<String, Database> databases = new HashMap<String, Database>();
	
	private FindDuplicatesAgentState() {
		File environmentFile = new File(getPath());
		environmentFile.mkdirs();
	
		try {
			EnvironmentConfig environmentConfig = new EnvironmentConfig();
			environmentConfig.setAllowCreate(true);
			environment = new Environment(environmentFile, environmentConfig);
	
			Activator.getDefault().getBundle().getBundleContext().addBundleListener(new SynchronousBundleListener() {
	
				public void bundleChanged(BundleEvent event) {
					if (event.getType() == BundleEvent.STOPPING)
						close();
				}
	
			});
		} catch (DatabaseException e) {
			log.error("Failed to open find duplicates agent state environment", e);
		}
	}
	
	public static String getPath() {
		return Platform.getInstanceLocation().getURL().getPath().substring(1) + "findDuplicates";
	}

	public static FindDuplicatesAgentState getDefault() {
		return singleton;
	}
	
	protected void close() {
		if (environment != null) {
			log.info("Closing the find duplicates agent state...");
			
			for (Database database : databases.values()) {
				try {
					database.close();
				} catch (DatabaseException e) {
					log.error("Failed to close the find duplicates agent state database", e);
				}
			}
			
			databases.clear();
			
			try {
				environment.close();
			} catch (DatabaseException e) {
				log.error("Failed to close find duplicates agent state environment", e);
			}
			
			environment = null;
		}
	}
	
	protected Database getDatabase(String agentId) {		
		Database database = databases.get(agentId);
		if (database == null) {
			try {
				DatabaseConfig databaseConfig = new DatabaseConfig();
				databaseConfig.setAllowCreate(true);
				database = environment.openDatabase(null, agentId, databaseConfig);
				databases.put(agentId, database);
			} catch (DatabaseException e) {
				log.error("Failed to open find duplicates agent state database", e);
			}
		}
		
		return database;
	}
	
	public Date getState(String agentId, IPerson person) {
		if (environment == null)
			return null;
		
		try {
			DatabaseEntry key = new DatabaseEntry();
			new PersistenceBinding().objectToEntry(person, key);
			
			DatabaseEntry data = new DatabaseEntry();

			if (getDatabase(agentId).get(null, key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
				Long millis = (Long) TupleBinding.getPrimitiveBinding(Long.class).entryToObject(data);
				return new Date(millis);
			}
		} catch (DatabaseException e) {
			log.error("Failed to get state for " + person, e);
		}
		
		return null;
	}
	
	public void putState(String agentId, IPerson person, Date state) {
		if (environment == null)
			return;
		
		try {
			DatabaseEntry key = new DatabaseEntry();
			new PersistenceBinding().objectToEntry(person, key);
			
			DatabaseEntry data = new DatabaseEntry();
			TupleBinding.getPrimitiveBinding(Long.class).objectToEntry(new Long(state.getTime()), data);

			getDatabase(agentId).put(null, key, data);
		} catch (DatabaseException e) {
			log.error("Failed to put state for " + person, e);
		}
	}

}
